]> git.saurik.com Git - apple/security.git/blob - OSX/Keychain Circle Notification/KNAppDelegate.m
Security-58286.1.32.tar.gz
[apple/security.git] / OSX / Keychain Circle Notification / KNAppDelegate.m
1 /*
2 * Copyright (c) 2013-2014 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24
25 #import "KNAppDelegate.h"
26 #import "KDSecCircle.h"
27 #import "KDCirclePeer.h"
28 #import "NSDictionary+compactDescription.h"
29 #import <AOSUI/NSImageAdditions.h>
30 #import <AppleSystemInfo/AppleSystemInfo.h>
31 #import <Security/SecFrameworkStrings.h>
32 #import "notify.h"
33 #import <utilities/debugging.h>
34 #import <os/variant_private.h>
35
36 #import <Accounts/Accounts.h>
37 #import <AOSAccounts/MobileMePrefsCoreAEPrivate.h>
38 #import <AOSAccounts/MobileMePrefsCore.h>
39 #import <AOSAccounts/ACAccountStore+iCloudAccount.h>
40 #import <AOSAccounts/iCloudAccount.h>
41
42 #include <msgtracer_client.h>
43 #include <msgtracer_keys.h>
44 #include <CrashReporterSupport/CrashReporterSupportPrivate.h>
45 #import <ProtectedCloudStorage/CloudIdentity.h>
46 #import "CoreCDP/CDPFollowUpController.h"
47 #import "CoreCDP/CDPFollowUpContext.h"
48 #import <CoreCDP/CDPAccount.h>
49
50 static const char * const kLaunchLaterXPCName = "com.apple.security.Keychain-Circle-Notification-TICK";
51 static const NSString * const kKickedOutKey = @"KickedOut";
52 static const NSString * const kValidOnlyOutOfCircleKey = @"ValidOnlyOutOfCircle";
53 static const NSString * const kPasswordChangedOrTrustedDeviceChanged = @"TDorPasswordChanged";
54 static NSString *prefpane = @"/System/Library/PreferencePanes/iCloudPref.prefPane";
55 #define kPublicKeyNotAvailable "com.apple.security.publickeynotavailable"
56 #define kPublicKeyAvailable "com.apple.security.publickeyavailable"
57 static NSString *KeychainPCDetailsAEAction = @"AKPCDetailsAEAction";
58 bool _hasPostedAndStillInError = false;
59 bool _haveCheckedForICDPStatusOnceInCircle = false;
60 bool _isAccountICDP = false;
61
62 @implementation KNAppDelegate
63
64 static NSUserNotificationCenter *appropriateNotificationCenter()
65 {
66 return [NSUserNotificationCenter _centerForIdentifier: @"com.apple.security.keychain-circle-notification"
67 type: _NSUserNotificationCenterTypeSystem];
68 }
69
70 static void PSKeychainSyncIsUsingICDP(void)
71 {
72 ACAccountStore *accountStore = [[ACAccountStore alloc] init];
73 ACAccount *primaryiCloudAccount = nil;
74
75 if ([accountStore respondsToSelector:@selector(icaPrimaryAppleAccount)]){
76 primaryiCloudAccount = [accountStore icaPrimaryAppleAccount];
77 }
78
79 NSString *dsid = primaryiCloudAccount.icaPersonID;
80 BOOL isICDPEnabled = NO;
81 if (dsid) {
82 isICDPEnabled = [CDPAccount isICDPEnabledForDSID:dsid];
83 NSLog(@"iCDP: PSKeychainSyncIsUsingICDP returning %@", isICDPEnabled ? @"TRUE" : @"FALSE");
84 } else {
85 NSLog(@"iCDP: no primary account");
86 }
87
88 _isAccountICDP = isICDPEnabled;
89 }
90
91 -(void) startFollowupKitRepair
92 {
93 NSError *localError = NULL;
94 CDPFollowUpController *cdpd = [[CDPFollowUpController alloc] init];
95 CDPFollowUpContext *context = [CDPFollowUpContext contextForStateRepair];
96 [cdpd postFollowUpWithContext:context error:&localError ];
97 if(localError){
98 secnotice("kcn", "request to CoreCDP to follow up failed: %@", localError);
99 }
100 else{
101 secnotice("kcn", "CoreCDP handling follow up");
102 _hasPostedAndStillInError = false;
103 }
104 }
105
106 - (void) handleDismissedNotification
107 {
108 if(_isAccountICDP){
109 secnotice("kcn", "handling dismissed notification, would start a follow up");
110 [self startFollowupKitRepair];
111 }
112 else
113 secerror("unable to find primary account");
114 }
115
116 - (void) notifyiCloudPreferencesAbout: (NSString *) eventName
117 {
118 if (eventName == nil)
119 return;
120
121 secnotice("kcn", "notifyiCloudPreferencesAbout %@", eventName);
122
123 NSString *accountID = (__bridge_transfer NSString*)(MMCopyLoggedInAccountFromAccounts());
124 ACAccountStore *accountStore = [[ACAccountStore alloc] init];
125 ACAccount *primaryiCloudAccount = nil;
126
127 if ([accountStore respondsToSelector:@selector(icaPrimaryAppleAccount)]){
128 primaryiCloudAccount = [accountStore icaPrimaryAppleAccount];
129 }
130
131 if(primaryiCloudAccount){
132 AEDesc aeDesc;
133 BOOL createdAEDesc = createAEDescWithAEActionAndAccountID((__bridge NSString *) kMMServiceIDKeychainSync, eventName, accountID, &aeDesc);
134 if (createdAEDesc) {
135 NSArray *prefPaneURL = [NSArray arrayWithObject: [NSURL fileURLWithPath: prefpane ]];
136
137 LSLaunchURLSpec lsSpec = {
138 .appURL = NULL,
139 .itemURLs = (__bridge CFArrayRef)prefPaneURL,
140 .passThruParams = &aeDesc,
141 .launchFlags = kLSLaunchDefaults | kLSLaunchAsync,
142 .asyncRefCon = NULL,
143 };
144
145 OSErr err = LSOpenFromURLSpec(&lsSpec, NULL);
146
147 if (err)
148 secerror("Can't send event %@, err=%d", eventName, err);
149 AEDisposeDesc(&aeDesc);
150 } else {
151 secerror("unable to create and send aedesc for account: '%@' and action: '%@'\n", primaryiCloudAccount, eventName);
152 }
153 }
154 secerror("unable to find primary account");
155 }
156
157 - (void) timerCheck
158 {
159 NSDate *nowish = [NSDate new];
160
161 self.state = [KNPersistentState loadFromStorage];
162 if ([nowish compare:self.state.pendingApplicationReminder] != NSOrderedAscending) {
163 secnotice("kcn", "REMINDER TIME: %@ >>> %@", nowish, self.state.pendingApplicationReminder);
164
165 // self.circle.rawStatus might not be valid yet
166 if (SOSCCThisDeviceIsInCircle(NULL) == kSOSCCRequestPending) {
167 // Still have a request pending, send reminder, and also in addtion to the UI
168 // we need to send a notification for iCloud pref pane to pick up
169 CFNotificationCenterPostNotificationWithOptions(
170 CFNotificationCenterGetDistributedCenter(),
171 CFSTR("com.apple.security.secureobjectsync.pendingApplicationReminder"),
172 (__bridge const void *) [self.state.applicationDate description], NULL, 0
173 );
174
175 [self postApplicationReminder];
176 self.state.pendingApplicationReminder = [nowish dateByAddingTimeInterval:[self getPendingApplicationReminderInterval]];
177 [self.state writeToStorage];
178 }
179 }
180 }
181
182
183 - (void) scheduleActivityAt: (NSDate *) time
184 {
185 if ([time compare:[NSDate distantFuture]] != NSOrderedSame) {
186 NSTimeInterval howSoon = [time timeIntervalSinceNow];
187 if (howSoon > 0)
188 [self scheduleActivityIn:ceil(howSoon)];
189 else
190 [self timerCheck];
191 }
192 }
193
194
195 - (void) scheduleActivityIn: (int) alertInterval
196 {
197 xpc_object_t options = xpc_dictionary_create(NULL, NULL, 0);
198 xpc_dictionary_set_uint64(options, XPC_ACTIVITY_DELAY, alertInterval);
199 xpc_dictionary_set_uint64(options, XPC_ACTIVITY_GRACE_PERIOD, XPC_ACTIVITY_INTERVAL_1_MIN);
200 xpc_dictionary_set_bool (options, XPC_ACTIVITY_REPEATING, false);
201 xpc_dictionary_set_bool (options, XPC_ACTIVITY_ALLOW_BATTERY, true);
202 xpc_dictionary_set_string(options, XPC_ACTIVITY_PRIORITY, XPC_ACTIVITY_PRIORITY_UTILITY);
203
204 xpc_activity_register(kLaunchLaterXPCName, options, ^(xpc_activity_t activity) {
205 [self timerCheck];
206 });
207 }
208
209
210 - (NSTimeInterval) getPendingApplicationReminderInterval
211 {
212 if (self.state.pendingApplicationReminderInterval)
213 return [self.state.pendingApplicationReminderInterval doubleValue];
214 else
215 return 24*60*60;
216 }
217
218
219 #define ICKC_EVENT_DISABLED "com.apple.security.secureobjectsync.disabled"
220 #define ICKC_EVENT_DEPARTURE_REASON "com.apple.security.secureobjectsync.departurereason"
221 #define ICKC_EVENT_NUM_PEERS "com.apple.security.secureobjectsync.numcircledevices"
222
223 - (void) applicationDidFinishLaunching: (NSNotification *) aNotification
224 {
225 appropriateNotificationCenter().delegate = self;
226 int out_taken;
227 int available;
228 secnotice("kcn", "Posted at launch: %@", appropriateNotificationCenter().deliveredNotifications);
229
230 notify_register_dispatch(kPublicKeyAvailable, &available, dispatch_get_main_queue(), ^(int token) {
231 CFErrorRef err = NULL;
232 KNAppDelegate *me = self;
233 SOSCCStatus currentCircleStatus = SOSCCThisDeviceIsInCircle(&err);
234 me.state = [KNPersistentState loadFromStorage];
235
236 secnotice("kcn", "got public key available notification");
237
238 me.state.lastCircleStatus = currentCircleStatus;
239
240 [me.state writeToStorage];
241 });
242
243 //register for public key not available notification, if occurs KCN can react
244 notify_register_dispatch(kPublicKeyNotAvailable, &out_taken, dispatch_get_main_queue(), ^(int token) {
245 CFErrorRef err = NULL;
246 KNAppDelegate *me = self;
247 enum DepartureReason departureReason = SOSCCGetLastDepartureReason(&err);
248 SOSCCStatus currentCircleStatus = SOSCCThisDeviceIsInCircle(&err);
249 me.state = [KNPersistentState loadFromStorage];
250
251 secnotice("kcn", "got public key not available notification, but won't send notification unless circle transition matches");
252 secnotice("kcn", "current circle status: %d, current departure reason: %d, last circle status: %d", currentCircleStatus, departureReason, me.state.lastCircleStatus);
253
254 PSKeychainSyncIsUsingICDP();
255
256 if(_isAccountICDP){
257 if((currentCircleStatus == kSOSCCError || currentCircleStatus == kSOSCCCircleAbsent || currentCircleStatus == kSOSCCNotInCircle) && _hasPostedAndStillInError == false) {
258 secnotice("kcn", "iCDP: device not in circle, posting follow up");
259 [self postRequirePassword];
260 _hasPostedAndStillInError = true;
261 }
262 else if(currentCircleStatus == kSOSCCInCircle){
263 secnotice("kcn", "iCDP: device is in circle!");
264 _hasPostedAndStillInError = false;
265 }
266 }
267 else if(!_isAccountICDP && currentCircleStatus == kSOSCCError && me.state.lastCircleStatus == kSOSCCInCircle && (departureReason == kSOSNeverLeftCircle)) {
268 secnotice("kcn", "circle status went from in circle to not in circle");
269 [self postRequirePassword];
270 }
271 me.state.lastCircleStatus = currentCircleStatus;
272
273 [me.state writeToStorage];
274 });
275
276 self.viewedIds = [NSMutableSet new];
277 self.circle = [KDSecCircle new];
278 KNAppDelegate *me = self;
279
280 [self.circle addChangeCallback:^{
281 secnotice("kcn", "{ChangeCallback}");
282
283 CFErrorRef err = NULL;
284
285 NSDate *nowish = [NSDate date];
286 enum DepartureReason departureReason = SOSCCGetLastDepartureReason(&err);
287 SOSCCStatus circleStatus = SOSCCThisDeviceIsInCircle(&err);
288 me.state = [KNPersistentState loadFromStorage];
289 secnotice("kcn", "applicationDidFinishLaunching");
290
291 PSKeychainSyncIsUsingICDP();
292
293 if(_isAccountICDP){
294 if((circleStatus == kSOSCCError || circleStatus == kSOSCCCircleAbsent || circleStatus == kSOSCCNotInCircle) && _hasPostedAndStillInError == false) {
295
296 secnotice("kcn", "ICDP: We need the password to re-validate ourselves - it's changed on another device");
297 me.state.lastCircleStatus = circleStatus;
298 [me.state writeToStorage];
299 [me postRequirePassword];
300 _hasPostedAndStillInError = true;
301 }
302 else if(circleStatus == kSOSCCInCircle){
303 secnotice("kcn", "iCDP: device is in circle!");
304 _hasPostedAndStillInError = false;
305 }
306 }
307 else if(!_isAccountICDP && circleStatus == kSOSCCError && me.state.lastCircleStatus == kSOSCCInCircle && (departureReason == kSOSNeverLeftCircle)) {
308 secnotice("kcn", "SA: circle status went from in circle to not in circle");
309 [me postRequirePassword];
310 }
311 // Pending application reminder
312 secnotice("kcn", "{ChangeCallback} scheduleActivity %@", me.state.pendingApplicationReminder);
313 if (circleStatus == kSOSCCRequestPending)
314 [me scheduleActivityAt:me.state.pendingApplicationReminder];
315
316
317 // No longer in circle?
318 if ((me.state.lastCircleStatus == kSOSCCInCircle && (circleStatus == kSOSCCNotInCircle || circleStatus == kSOSCCCircleAbsent)) ||
319 (me.state.lastCircleStatus == kSOSCCCircleAbsent && circleStatus == kSOSCCNotInCircle && me.state.absentCircleWithNoReason) ||
320 me.state.debugLeftReason) {
321 enum DepartureReason reason = kSOSNeverLeftCircle;
322 if (me.state.debugLeftReason) {
323 reason = [me.state.debugLeftReason intValue];
324 me.state.debugLeftReason = nil;
325 [me.state writeToStorage];
326 } else {
327 reason = SOSCCGetLastDepartureReason(&err);
328 if (reason == kSOSDepartureReasonError) {
329 secnotice("kcn", "SOSCCGetLastDepartureReason err: %@", err);
330 }
331 if (err) CFRelease(err);
332 }
333
334 if (reason != kSOSDepartureReasonError) {
335 // Post kick-out alert
336
337 // <rdar://problem/20862435> MessageTracer data to find out how many users were dropped & reset
338 msgtracer_domain_t domain = msgtracer_domain_new(ICKC_EVENT_DISABLED);
339 msgtracer_msg_t mt_msg = NULL;
340
341 if (domain != NULL)
342 mt_msg = msgtracer_msg_new(domain);
343
344 if (mt_msg) {
345 char s[16];
346
347 msgtracer_set(mt_msg, kMsgTracerKeySignature, ICKC_EVENT_DEPARTURE_REASON);
348 snprintf(s, sizeof(s), "%u", reason);
349 msgtracer_set(mt_msg, kMsgTracerKeyValue, s);
350
351 int64_t num_peers = 0;
352 CFArrayRef peerList = SOSCCCopyPeerPeerInfo(NULL);
353 if (peerList) {
354 num_peers = CFArrayGetCount(peerList);
355 if (num_peers > 99) {
356 // Round down # peers to 2 significant digits
357 int factor;
358 for (factor = 10; num_peers >= 100*factor; factor *= 10) ;
359 num_peers = (num_peers / factor) * factor;
360 }
361 CFRelease(peerList);
362 }
363 msgtracer_set(mt_msg, kMsgTracerKeySignature2, ICKC_EVENT_NUM_PEERS);
364 snprintf(s, sizeof(s), "%lld", num_peers);
365 msgtracer_set(mt_msg, kMsgTracerKeyValue2, s);
366
367 msgtracer_set(mt_msg, kMsgTracerKeySummarize, "NO");
368 msgtracer_log(mt_msg, ASL_LEVEL_DEBUG, "");
369 }
370
371 // FIXME:
372 // 1. Write here due to [me timerCheck] => [KNPersistentState loadFromStorage] below?!?
373 // 2. Or change call order of timerCheck, pendingApplication reminder below???
374 me.state.absentCircleWithNoReason = (circleStatus == kSOSCCCircleAbsent && reason == kSOSNeverLeftCircle);
375 [me.state writeToStorage];
376 secnotice("kcn", "{ChangeCallback} departure reason %d", reason);
377
378 switch (reason) {
379 case kSOSDiscoveredRetirement:
380 case kSOSLostPrivateKey:
381 case kSOSWithdrewMembership:
382 case kSOSNeverAppliedToCircle:
383 break;
384
385 case kSOSNeverLeftCircle:
386 case kSOSMembershipRevoked:
387 case kSOSLeftUntrustedCircle:
388 default:
389 [me postKickedOutAlert: reason];
390 break;
391 }
392 }
393 }
394
395
396 // Circle applications: pending request(s) started / completed
397 if (me.circle.rawStatus != me.state.lastCircleStatus) {
398 SOSCCStatus lastCircleStatus = me.state.lastCircleStatus;
399 me.state.lastCircleStatus = circleStatus;
400
401 if (lastCircleStatus != kSOSCCRequestPending && circleStatus == kSOSCCRequestPending) {
402 secnotice("kcn", "{ChangeCallback} Pending request START");
403 me.state.applicationDate = nowish;
404 me.state.pendingApplicationReminder = [me.state.applicationDate dateByAddingTimeInterval:[me getPendingApplicationReminderInterval]];
405 [me.state writeToStorage]; // FIXME: move below? might be needed for scheduleActivityAt...
406 [me scheduleActivityAt:me.state.pendingApplicationReminder];
407 }
408
409 if (lastCircleStatus == kSOSCCRequestPending && circleStatus != kSOSCCRequestPending) {
410 secnotice("kcn", "Pending request completed");
411 me.state.applicationDate = [NSDate distantPast];
412 me.state.pendingApplicationReminder = [NSDate distantFuture];
413 [me.state writeToStorage];
414
415 // Remove reminders
416 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
417 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
418 if (note.userInfo[(NSString*) kValidOnlyOutOfCircleKey] && note.userInfo[@"ApplicationReminder"]) {
419 secnotice("kcn", "{ChangeCallback} Removing notification %@", note);
420 [appropriateNotificationCenter() removeDeliveredNotification: note];
421 }
422 }
423 }
424 }
425
426
427 // Clear out (old) reset notifications
428 if (me.circle.isInCircle) {
429 secnotice("kcn", "{ChangeCallback} me.circle.isInCircle");
430 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
431 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
432 if (note.userInfo[(NSString*) kValidOnlyOutOfCircleKey]) {
433 secnotice("kcn", "Removing existing notification (%@) now that we are in circle", note);
434 [appropriateNotificationCenter() removeDeliveredNotification: note];
435 }
436 }
437 }
438
439 //Clear out (old) password changed notifications
440 if(me.circle.isInCircle){
441 secnotice("kcn", "{ChangeCallback} me.circle.isInCircle");
442 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
443 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
444 if (note.userInfo[(NSString*) kPasswordChangedOrTrustedDeviceChanged]) {
445 secnotice("kcn", "Removing existing notification (%@) now that we are valid again", note);
446 [appropriateNotificationCenter() removeDeliveredNotification: note];
447 }
448 }
449
450 }
451
452 // Applicants
453 secnotice("kcn", "{ChangeCallback} Applicants");
454 NSMutableSet *applicantIds = [NSMutableSet new];
455 for (KDCirclePeer *applicant in me.circle.applicants) {
456 if (!me.circle.isInCircle) {
457 // Don't yammer on about circles we aren't in, and don't announce our own
458 // join requests as if the user could approve them locally!
459 break;
460 }
461 [me postForApplicant:applicant];
462 [applicantIds addObject:applicant.idString];
463 }
464
465
466 // Update notifications
467 NSUserNotificationCenter *notificationCenter = appropriateNotificationCenter();
468 secnotice("kcn", "Checking validity of %lu notes", (unsigned long)notificationCenter.deliveredNotifications.count);
469 for (NSUserNotification *note in notificationCenter.deliveredNotifications) {
470 if (note.userInfo[@"applicantId"] && ![applicantIds containsObject:note.userInfo[@"applicantId"]]) {
471 secnotice("kcn", "No longer an applicant (%@) for %@ (I=%@)", note.userInfo[@"applicantId"], note, [note.userInfo compactDescription]);
472 [notificationCenter removeDeliveredNotification:note];
473 } else {
474 secnotice("kcn", "Still an applicant (%@) for %@ (I=%@)", note.userInfo[@"applicantId"], note, [note.userInfo compactDescription]);
475 }
476 }
477
478 me.state.lastCircleStatus = circleStatus;
479
480 [me.state writeToStorage];
481 }];
482 }
483
484
485 - (BOOL) userNotificationCenter: (NSUserNotificationCenter *) center
486 shouldPresentNotification: (NSUserNotification *) notification
487 {
488 return YES;
489 }
490
491
492 - (void) userNotificationCenter: (NSUserNotificationCenter *) center
493 didActivateNotification: (NSUserNotification *) notification
494 {
495 if (notification.activationType == NSUserNotificationActivationTypeActionButtonClicked) {
496 [self notifyiCloudPreferencesAbout:notification.userInfo[@"Activate"]];
497 }
498 }
499
500
501 - (void) userNotificationCenter: (NSUserNotificationCenter *) center
502 didDismissAlert: (NSUserNotification *) notification
503 {
504 [self handleDismissedNotification];
505
506 // If we don't do anything here & another notification comes in we
507 // will repost the alert, which will be dumb.
508 id applicantId = notification.userInfo[@"applicantId"];
509 if (applicantId != nil) {
510 [self.viewedIds addObject:applicantId];
511 }
512 }
513
514
515 - (void) postForApplicant: (KDCirclePeer *) applicant
516 {
517 static int postCount = 0;
518
519 if ([self.viewedIds containsObject:applicant.idString]) {
520 secnotice("kcn", "Already viewed %@, skipping", applicant);
521 return;
522 }
523
524 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
525 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
526 if ([applicant.idString isEqualToString:note.userInfo[@"applicantId"]]) {
527 if (note.isPresented) {
528 secnotice("kcn", "Already posted&presented: %@ (I=%@)", note, note.userInfo);
529 return;
530 } else {
531 secnotice("kcn", "Already posted, but not presented: %@ (I=%@)", note, note.userInfo);
532 }
533 }
534 }
535
536 NSUserNotification *note = [NSUserNotification new];
537 note.title = [NSString stringWithFormat: (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_APPROVAL_TITLE), applicant.name];
538 note.informativeText = [KNAppDelegate localisedApprovalBodyWithDeviceTypeFromPeerInfo:applicant.peerObject];
539 note._displayStyle = _NSUserNotificationDisplayStyleAlert;
540 note._identityImage = [NSImage bundleImage];
541 note._identityImageStyle = _NSUserNotificationIdentityImageStyleRectangleNoBorder;
542 note.otherButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_DECLINE);
543 note.actionButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_APPROVE);
544 note.identifier = [[NSUUID new] UUIDString];
545 note.userInfo = @{
546 @"applicantName": applicant.name,
547 @"applicantId" : applicant.idString,
548 @"Activate" : (__bridge NSString *) kMMPropertyKeychainAADetailsAEAction,
549 };
550
551 secnotice("kcn", "About to post #%d/%lu (%@): %@", postCount, noteCenter.deliveredNotifications.count, applicant.idString, note);
552 [appropriateNotificationCenter() deliverNotification:note];
553 postCount++;
554 }
555
556 + (NSString *)localisedApprovalBodyWithDeviceTypeFromPeerInfo:(id)peerInfo {
557 NSString *type = (__bridge NSString *)SOSPeerInfoGetPeerDeviceType((__bridge SOSPeerInfoRef)(peerInfo));
558 CFStringRef localisedType = NULL;
559 if ([type isEqualToString:@"iPad"]) {
560 localisedType = SecCopyCKString(SEC_CK_APPROVAL_BODY_OSX_IPAD);
561 } else if ([type isEqualToString:@"iPhone"]) {
562 localisedType = SecCopyCKString(SEC_CK_APPROVAL_BODY_OSX_IPHONE);
563 } else if ([type isEqualToString:@"iPod"]) {
564 localisedType = SecCopyCKString(SEC_CK_APPROVAL_BODY_OSX_IPOD);
565 } else if ([type isEqualToString:@"Mac"]) {
566 localisedType = SecCopyCKString(SEC_CK_APPROVAL_BODY_OSX_MAC);
567 } else {
568 localisedType = SecCopyCKString(SEC_CK_APPROVAL_BODY_OSX_GENERIC);
569 }
570 return (__bridge_transfer NSString *)localisedType;
571 }
572
573 - (void) postRequirePassword
574 {
575 if(!_isAccountICDP){
576 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
577 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
578 if (note.userInfo[(NSString*) kPasswordChangedOrTrustedDeviceChanged]) {
579 if (note.isPresented) {
580 secnotice("kcn", "Already posted & presented: %@", note);
581 [appropriateNotificationCenter() removeDeliveredNotification: note];
582 } else {
583 secnotice("kcn", "Already posted, but not presented: %@", note);
584 }
585 }
586 }
587
588 NSString *message = CFBridgingRelease(SecCopyCKString(SEC_CK_PWD_REQUIRED_BODY_OSX));
589 if (os_variant_has_internal_ui("iCloudKeychain")) {
590 NSString *reason_str = [NSString stringWithFormat:(__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CR_REASON_INTERNAL), @"Device became untrusted or password changed"];
591 message = [message stringByAppendingString: reason_str];
592 }
593
594 NSUserNotification *note = [NSUserNotification new];
595 note.title = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_PWD_REQUIRED_TITLE);
596 note.informativeText = message;
597 note._identityImage = [NSImage bundleImage];
598 note._identityImageStyle = _NSUserNotificationIdentityImageStyleRectangleNoBorder;
599 note.otherButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_NOT_NOW);
600 note.actionButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CONTINUE);
601 note.identifier = [[NSUUID new] UUIDString];
602
603 note.userInfo = @{
604 kPasswordChangedOrTrustedDeviceChanged : @1,
605 @"Activate" : (__bridge NSString *) kMMPropertyKeychainPCDetailsAEAction,
606 };
607
608 secnotice("kcn", "body=%@", note.informativeText);
609 secnotice("kcn", "About to post #-/%lu (PASSWORD/TRUSTED DEVICE): %@", noteCenter.deliveredNotifications.count, note);
610 [appropriateNotificationCenter() deliverNotification:note];
611 }
612 else{
613 secnotice("kcn","would have posted needs password and then followed up");
614 [self startFollowupKitRepair];
615 }
616 }
617
618 - (void) postKickedOutAlert: (int) reason
619 {
620
621 if(!_isAccountICDP){
622 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
623 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
624 if (note.userInfo[(NSString*) kKickedOutKey]) {
625 if (note.isPresented) {
626 secnotice("kcn", "Already posted&presented (removing): %@", note);
627 [appropriateNotificationCenter() removeDeliveredNotification: note];
628 } else {
629 secnotice("kcn", "Already posted, but not presented: %@", note);
630 }
631 }
632 }
633
634 NSString *message = CFBridgingRelease(SecCopyCKString(SEC_CK_PWD_REQUIRED_BODY_OSX));
635 if (os_variant_has_internal_ui("iCloudKeychain")) {
636 static const char *departureReasonStrings[] = {
637 "kSOSDepartureReasonError",
638 "kSOSNeverLeftCircle",
639 "kSOSWithdrewMembership",
640 "kSOSMembershipRevoked",
641 "kSOSLeftUntrustedCircle",
642 "kSOSNeverAppliedToCircle",
643 "kSOSDiscoveredRetirement",
644 "kSOSLostPrivateKey",
645 "unknown reason"
646 };
647 int idx = (kSOSDepartureReasonError <= reason && reason <= kSOSLostPrivateKey) ? reason : (kSOSLostPrivateKey + 1);
648 NSString *reason_str = [NSString stringWithFormat:(__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CR_REASON_INTERNAL), departureReasonStrings[idx]];
649 message = [message stringByAppendingString: reason_str];
650 }
651
652 // <rdar://problem/21988060> Improve wording of the iCloud keychain drop/reset error messages
653 // Contrary to HI spec (and I think it makes more sense)
654 // 1. otherButton == top : Not Now
655 // 2. actionButton == bottom: Continue
656 // 3. If we followed HI spec, replace "Activate" => "Dismiss" in note.userInfo below
657 NSUserNotification *note = [NSUserNotification new];
658 note.title = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_PWD_REQUIRED_TITLE);
659 note.informativeText = message;
660 note._identityImage = [NSImage bundleImage];
661 note._identityImageStyle = _NSUserNotificationIdentityImageStyleRectangleNoBorder;
662 note.otherButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_NOT_NOW);
663 note.actionButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CONTINUE);
664 note.identifier = [[NSUUID new] UUIDString];
665
666 note.userInfo = @{
667 kKickedOutKey : @1,
668 kValidOnlyOutOfCircleKey: @1,
669 @"Activate" : (__bridge NSString *) kMMPropertyKeychainMRDetailsAEAction,
670 };
671
672 secnotice("kcn", "body=%@", note.informativeText);
673 secnotice("kcn", "About to post #-/%lu (KICKOUT): %@", noteCenter.deliveredNotifications.count, note);
674 [appropriateNotificationCenter() deliverNotification:note];
675 }
676
677 else{
678 secnotice("kcn","postKickedOutAlert starting followup repair");
679 [self startFollowupKitRepair];
680 }
681 }
682
683 - (void) postApplicationReminder
684 {
685 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
686 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
687 if (note.userInfo[@"ApplicationReminder"]) {
688 if (note.isPresented) {
689 secnotice("kcn", "Already posted&presented (removing): %@", note);
690 [appropriateNotificationCenter() removeDeliveredNotification: note];
691 } else {
692 secnotice("kcn", "Already posted, but not presented: %@", note);
693 }
694 }
695 }
696
697 // <rdar://problem/21988060> Improve wording of the iCloud keychain drop/reset error messages
698 // Contrary to HI spec (and I think it makes more sense)
699 // 1. otherButton == top : Not Now
700 // 2. actionButton == bottom: Continue
701 // 3. If we followed HI spec, replace "Activate" => "Dismiss" in note.userInfo below
702 NSUserNotification *note = [NSUserNotification new];
703 note.title = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_REMINDER_TITLE_OSX);
704 note.informativeText = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_REMINDER_BODY_OSX);
705 note._identityImage = [NSImage bundleImage];
706 note._identityImageStyle = _NSUserNotificationIdentityImageStyleRectangleNoBorder;
707 note.otherButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_NOT_NOW);
708 note.actionButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CONTINUE);
709 note.identifier = [[NSUUID new] UUIDString];
710
711 note.userInfo = @{
712 @"ApplicationReminder" : @1,
713 kValidOnlyOutOfCircleKey: @1,
714 @"Activate" : (__bridge NSString *) kMMPropertyKeychainWADetailsAEAction,
715 };
716
717 secnotice("kcn", "About to post #-/%lu (REMINDER): %@ (I=%@)", noteCenter.deliveredNotifications.count, note, [note.userInfo compactDescription]);
718 [appropriateNotificationCenter() deliverNotification:note];
719 }
720
721 @end